/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.base.i18n.support;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.iwindplus.base.domain.constant.CommonConstant.NumberConstant;
import com.iwindplus.base.domain.constant.CommonConstant.SymbolConstant;
import com.iwindplus.base.i18n.domain.constant.I18nConstant;
import java.io.IOException;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.AbstractResourceBasedMessageSource;

/**
 * nacos 国际化.
 *
 * @author zengdegui
 * @since 2025/03/03 21:46
 */
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NacosBundleMessageSource extends AbstractResourceBasedMessageSource {

    private static final String DEFAULT_GROUP = I18nConstant.I18N_GROUP;
    private static final String DEFAULT_FILE_SUFFIX = I18nConstant.FILE_SUFFIX;

    private Executor executor;
    private ConfigService configService;
    private String group = DEFAULT_GROUP;
    private Boolean variant = Boolean.TRUE;

    private final ConcurrentHashMap<String, Properties> cachedProperties = new ConcurrentHashMap<>(16);
    private final ConcurrentHashMap<String, Listener> registeredListeners = new ConcurrentHashMap<>(16);

    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        return getMergedProperties(locale).getProperty(code);
    }

    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        String msg = getMergedProperties(locale).getProperty(code);
        return CharSequenceUtil.isNotBlank(msg) ? new MessageFormat(msg, locale) : null;
    }

    private Properties getMergedProperties(Locale locale) {
        Properties mergedProperties = new Properties();

        List<String> fileNames = calculateFileNamesForLocale(locale);
        if (ObjectUtil.isEmpty(fileNames)) {
            return mergedProperties;
        }

        fileNames.forEach(fileName -> {
            Properties props = getPropertiesWithCache(fileName);
            if (props != null && !props.isEmpty()) {
                mergedProperties.putAll(props);
            }
        });

        return mergedProperties;
    }

    /**
     * 线程安全获取缓存， miss 时仅加载一次
     */
    private Properties getPropertiesWithCache(String fileName) {
        Properties props = cachedProperties.get(fileName);
        if (props == null) {
            synchronized (cachedProperties.computeIfAbsent(fileName, k -> new Properties())) {
                props = cachedProperties.get(fileName);
                if (props.isEmpty()) {
                    props = loadProperties(fileName);
                    cachedProperties.put(fileName, props == null ? new Properties() : props);
                }
            }
        }
        return props;
    }

    private List<String> calculateFileNamesForLocale(Locale locale) {
        return this.getBasenameSet().stream()
            .flatMap(basename -> {
                String baseName = CharSequenceUtil.contains(basename, SymbolConstant.SLASH)
                    ? CharSequenceUtil.subAfter(basename, SymbolConstant.SLASH, true)
                    : basename;

                List<String> fileNames = new ArrayList<>();
                fileNames.add(baseName);

                String language = locale.getLanguage();
                if (CharSequenceUtil.isNotBlank(language)) {
                    fileNames.add(String.format("%s_%s", baseName, language));
                }

                String country = locale.getCountry();
                if (CharSequenceUtil.isNotBlank(country)) {
                    fileNames.add(String.format("%s_%s_%s", baseName, language, country));
                }

                String variant = locale.getVariant();
                if (this.variant && CharSequenceUtil.isNotBlank(variant)) {
                    fileNames.add(String.format("%s_%s_%s_%s", baseName, language, country, variant));
                }

                return fileNames.stream();
            }).sorted((s1, s2) -> Integer.compare(s2.length(), s1.length()))
            .collect(Collectors.toList());
    }

    private Properties loadProperties(String fileName) {
        Properties properties = new Properties();
        String dataId = fileName + DEFAULT_FILE_SUFFIX;

        try {
            String configInfo = configService.getConfig(dataId, group, NumberConstant.NUMBER_THREE_THOUSAND);
            if (CharSequenceUtil.isNotBlank(configInfo)) {
                log.info("Load i18n from Nacos, dataId={}", dataId);
                properties.load(new StringReader(configInfo));
                registerListenerForDynamicRefresh(dataId, fileName);
            } else {
                createDefaultProperties(dataId);
            }
        } catch (NacosException | IOException e) {
            log.warn("Failed to load i18n from Nacos, dataId={}, fallback to empty", dataId, e);
        }
        return properties;
    }

    private void registerListenerForDynamicRefresh(String dataId, String fileName) throws NacosException {
        if (registeredListeners.containsKey(dataId)) {
            return;
        }

        Listener listener = new Listener() {
            @Override
            public Executor getExecutor() {
                return executor;
            }

            @Override
            public void receiveConfigInfo(String configInfo) {
                if (CharSequenceUtil.isBlank(configInfo)) {
                    return;
                }

                try {
                    log.info("Dynamic i18n refreshed from Nacos, dataId={}", dataId);
                    Properties newProperties = new Properties();
                    newProperties.load(new StringReader(configInfo));
                    cachedProperties.put(fileName, newProperties);
                } catch (IOException ex) {
                    log.error("Failed to load properties from new config info for dataId={}", dataId, ex);
                }
            }
        };

        configService.addListener(dataId, group, listener);
        registeredListeners.put(dataId, listener);
        log.info("Listener registered for dataId={}", dataId);
    }

    private void createDefaultProperties(String dataId) throws NacosException {
        String content = "# auto-created empty i18n file\n";
        configService.publishConfig(dataId, group, content, ConfigType.PROPERTIES.getType());
    }
}